package protocol.hdlc;
import java.io.BufferedReader;
import java.io.BufferedWriter;
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.OutputStreamWriter;
import java.net.ServerSocket;
import java.net.Socket;

import protocol.arq.ARQ;
import protocol.arq.ARQReceiver;
import protocol.arq.ARQSender;
import protocol.arq.MessageBuilder;
import protocol.arq.gobackN.GoBackNReceiver;
import utils.advancedSocket.AdvancedSocket;
import utils.advancedSocket.AdvancedSocketAdapter;
import utils.advancedSocket.AdvancedSocketCloseEvent;
import utils.trame.Trame;
import utils.trame.Trame.TrameType;

/**
 * Recoit des donnees d'une communication via un port et ecrit les donnees recus
 * dans un fichier.
 * 
 * @author Bertholet Damien & Daoust Julien
 */
public class Receiver extends HDLCProtocol {

	/**
	 * Lance l'ecoute des connexions sur le port souhaite.
	 * 
	 * @param args
	 *            Prend un seul argument qui est un entier.
	 */
	public static void main(String[] args) {
		String utilisation = "Utilisation : java Receiver <Numero_Port>";
		if(args.length != 1)
			throw new IllegalArgumentException(
			        "Number of arguments who are incorrects. " + utilisation);

		int port;
		try {
			port = Integer.parseInt(args[0]);
		} catch (NumberFormatException e) {
			throw new IllegalArgumentException(
			        "Port's number must be an integer. " + utilisation);
		}

		try {
			new Receiver(port);
		} catch (Exception e) {
			e.printStackTrace();
		}
	}

	/**
	 * Constructeur.
	 * 
	 * @param port
	 *            Le port de communication.
	 * @throws Exception
	 *             Si une erreur survient durant le processus.
	 */
	@SuppressWarnings("resource")
	public Receiver(int port) throws Exception {
		if(HDLCProtocol.VERBOSE) {
			System.out.println("Receive");
			System.out.println("Port :" + port + "\n");
		}

		ServerSocket serverSocket = new ServerSocket(port);

		// Ceci permettrait d'accepter plusieurs connexions meme si nous en
		// utiliseront qu'une seule dans notre contexte.
		while(true) {
			Socket socket = serverSocket.accept();
			if(HDLCProtocol.VERBOSE)
				System.out.println("\nSockect accepted");

			Receiver.manageConnection(socket);
		}
	}

	/**
	 * Gere la nouvelle connection.
	 * 
	 * @param socket
	 *            La {@link Socket} de la connection.
	 * @throws Exception
	 *             Si une erreur survient durant le processus.
	 */
	@SuppressWarnings("resource")
	private static void manageConnection(Socket socket) throws Exception {
		int receivingMethod = Receiver.getReceivingMethodId(socket);

		if(HDLCProtocol.VERBOSE)
			System.out
			        .println("Connection etablished whit the receivingMethod :" +
			                receivingMethod);

		AdvancedSocket advancedSocket = new AdvancedSocket(socket);

		File file = Receiver.findFile();
		final BufferedWriter bufferedWriter =
		        new BufferedWriter(new OutputStreamWriter(new FileOutputStream(
		                file), HDLCProtocol.charset));

		if(HDLCProtocol.VERBOSE)
			System.out.println("Destination data :" + file.getAbsolutePath() +
			        "\n");

		advancedSocket.addAdvancedSocketListener(new AdvancedSocketAdapter() {

			@Override
			public void onCloseConnection(AdvancedSocketCloseEvent event) {
				try {
					bufferedWriter.close();
				} catch (IOException e) {
					// Do nothing
				}
			}
		});

		// Instancie le ARQReceiver souhaite.
		Receiver.getReceivingMethod(receivingMethod, advancedSocket,
		        bufferedWriter);
	}

	/**
	 * Retourne l'id de la methode de reception souhaite par le client. Pour
	 * cela, on attend de recevoir le premier message qui fait une demande de
	 * connexion. On ignore tous les autres messages.
	 * 
	 * @param socket
	 *            La {@link Socket} de la connection.
	 * @return L'id de la methode de reception que le client souhaite.
	 * @throws IOException
	 *             Si une exception intervient durant le processus.
	 */
	private static int getReceivingMethodId(Socket socket) throws IOException {
		MessageBuilder messageBuilder = new MessageBuilder(HDLCProtocol.FANION);

		BufferedReader reader =
		        new BufferedReader(new InputStreamReader(
		                socket.getInputStream()));

		Trame trame;
		while(true) {
			boolean isComplete = messageBuilder.add((char) reader.read());

			if(isComplete) {
				trame = ARQ.decodeTrame(messageBuilder.putFirstMessage());
				if(trame.getType() == TrameType.C)
					break;
			}
		}

		return trame.getNum();
	}

	/**
	 * Retourne un object {@link ARQReceiver} qui represente la methode de
	 * reception choisit.
	 * 
	 * @param receivingMethod
	 *            La methode de reception des donnees (0 pour Go-Back-N, ...).
	 * @param advancedSocket
	 *            L'{@link AdvancedSocket}.
	 * @param writer
	 *            Le {@link BufferedWriter} pour ecrire les donnees recues.
	 * @return L'{@link ARQSender}.
	 * @throws Exception
	 *             Si une erreur survient durant la creation de la methode.
	 */
	private static ARQReceiver getReceivingMethod(int receivingMethod,
	        AdvancedSocket advancedSocket, BufferedWriter writer)
	        throws Exception {
		switch(receivingMethod) {
		case 0:
			return new GoBackNReceiver(advancedSocket, writer);
		default:
			throw new IllegalArgumentException("Receiving method unknown :" +
			        receivingMethod);
		}
	}

	/**
	 * Trouve le prochain fichier disponible et l'ouvre dans un
	 * {@link BufferedWriter}.
	 * 
	 * @return Le {@link BufferedWriter} contenant le prochain fichier
	 *         disponible.
	 * @throws IOException
	 *             Une erreur est servenue.
	 */
	private static File findFile() throws IOException {
		int id = -1;
		File file;
		do {
			id++;
			file = new File("dat/receiver/" + id);
		} while(file.exists());
		file.createNewFile();

		return file;
	}
}